package com.bolt.admin.security.shiro.filter;


import com.bolt.admin.exception.BizExceptionEnum;
import com.bolt.admin.security.shiro.AuthContextHolder;
import com.bolt.admin.security.shiro.AuthUserDetails;
import com.bolt.admin.security.shiro.exception.AuthErrorException;
import com.bolt.admin.module.sys.entity.LoginLogEntity;
import com.bolt.admin.module.sys.entity.UserEntity;
import com.bolt.admin.module.sys.service.UserService;
import com.bolt.common.utils.SpringContextUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.common.web.RequestUtil;
import com.bolt.support.cache.redis.RedisManager;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;


public class SessionFormPasswordFilter extends FormAuthenticationFilter {

    /**
     * 记录用户输入的用户名信息，用于登录界面回显
     */
    public static final String KEY_AUTH_USERNAME_VALUE = "lastUserName";


    public static final String VALIDATE_CODE_PARAM = "captcha";

    public static final String VALIDATE_ID_PARAM = "graphId";

    private String successUrl = "/";

    private boolean captchaEnabled;


    private UserService getUserProvider() {
        return SpringContextUtil.getBean(UserService.class);
    }


    @Override
    protected AuthenticationToken createToken(String username, String password, ServletRequest request, ServletResponse response) {
        boolean rememberMe = isRememberMe(request);
        String host = getHost(request);
        return new UsernamePasswordToken(username, password, rememberMe, host);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                //本次用户登陆账号
                String username = this.getUsername(request);
                Subject subject = this.getSubject(request, response);
                //之前登陆的用户
                AuthUserDetails preAuthUserDetails = AuthContextHolder.getAuthUserDetails();
                //如果两次登陆的用户不一样，则先退出之前登陆的用户
                if (username != null && preAuthUserDetails != null && !username.equals(preAuthUserDetails.getUserName())) {
                    subject.logout();
                }
            }
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                return this.executeLogin(request, response);
            } else {
                return true;
            }
        } else {
            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }

    protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
        return request instanceof HttpServletRequest && WebUtils.toHttp(request).getMethod().equalsIgnoreCase("POST");
    }


    public boolean validateCaptchaCode(HttpServletRequest request, HttpServletResponse response) {
        String validateCode = request.getParameter(VALIDATE_CODE_PARAM);
        String graphId = request.getParameter(VALIDATE_ID_PARAM);
        RedisManager redisManager = SpringContextUtil.getBean(RedisManager.class);

        // 查询验证码
        String code = redisManager.get(graphId);
        // 清除验证码
        redisManager.del(graphId);
        if (StrUtil.isBlank(code)) {
            throw new AuthErrorException(BizExceptionEnum.AUTH_CAPTCHA_EXPIRED_ERROR);
        }
        if (StrUtil.isBlank(validateCode) || !validateCode.equalsIgnoreCase(code)) {
            throw new AuthErrorException(BizExceptionEnum.AUTH_CAPTCHA_VERIFY_ERROR);
        }
        return true;
    }

    protected boolean executeLogin(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        UsernamePasswordToken token = (UsernamePasswordToken) createToken(request, response);
        ;
        UserEntity account = null;
        try {
            // 验证码禁用 或不是表单提交 允许访问
            if (captchaEnabled) {
                validateCaptchaCode(request, response);
            }
            String username = token.getUsername();
            if (getUserProvider().isUserLock(username)) {
                throw new AuthErrorException(BizExceptionEnum.ACCOUNT_LOCKED);
            }
            account = getUserProvider().findByUserName(username).orElseThrow(() ->
                    new AuthErrorException(BizExceptionEnum.USER_NOT_EXISTED));

            Subject subject = getSubject(request, response);
            subject.login(token);
            return onLoginSuccess(account, token, subject, request, response);

        } catch (AuthenticationException e) {
            if (e instanceof IncorrectCredentialsException) {
                e = new AuthErrorException(BizExceptionEnum.AUTH_REQUEST_ERROR);
            }
            return onLoginFailure(account, token, e, request, response);
        }
    }

    /**
     * 记录当登录失败次数
     */
    protected boolean onLoginFailure(UserEntity account, AuthenticationToken token,
                                     AuthenticationException exception,
                                     ServletRequest request, ServletResponse response) throws Exception {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String username = usernamePasswordToken.getUsername();
        getUserProvider().setfailureLimit(username);
        //写入登录账号名称用于回显
        request.setAttribute(KEY_AUTH_USERNAME_VALUE, username);
        return super.onLoginFailure(token, exception, request, response);

    }

    @Override
    protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
        //写入认证异常对象用于错误显示
        request.setAttribute(getFailureKeyAttribute(), ae);
    }

    /**
     * 重写父类方法，当登录成功后，重置失败标志
     */
    protected boolean onLoginSuccess(UserEntity user, AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        LocalDateTime now = LocalDateTime.now();
        //写入登入记录信息
        LoginLogEntity userLoginLog = new LoginLogEntity();
        userLoginLog.setUser(user);
        userLoginLog.setLogonTime(now);
        userLoginLog.setRemoteAddr(httpServletRequest.getRemoteAddr());
        userLoginLog.setRemoteHost(httpServletRequest.getRemoteHost());
        userLoginLog.setRemotePort(httpServletRequest.getRemotePort());


        userLoginLog.setUserAgent(httpServletRequest.getHeader("User-Agent"));
        userLoginLog.setXforwardFor(RequestUtil.getRemoteIpAddress(httpServletRequest));


        getUserProvider().saveLoginLog(userLoginLog);
        if (RequestUtil.isAjaxRequest(httpServletRequest) && StrUtil.isNotBlank(getSuccessUrl())) {
            return true;
        } else {
            WebUtils.redirectToSavedRequest(request, response, this.getSuccessUrl());
            return false;
        }

    }

    public String getSuccessUrl() {
        return successUrl;
    }

    public void setSuccessUrl(String successUrl) {
        this.successUrl = successUrl;
    }

    public boolean isCaptchaEnabled() {
        return captchaEnabled;
    }

    public void setCaptchaEnabled(boolean captchaEnabled) {
        this.captchaEnabled = captchaEnabled;
    }
}
